package com.zd.lib.http;

import android.support.annotation.NonNull;


import com.zd.lib.http.okhttp.ProgressListener;
import com.zd.lib.http.okhttp.ProgressRequestInterceptor;
import com.zd.lib.http.okhttp.ProgressResponseInterceptor;
import com.zd.lib.http.response.ImageUpEntity;
import com.zd.lib.http.retrofit.RetrofitUtils;
import com.zd.lib.http.retrofit.utils.SchedulerCompat;
import com.zd.lib.utils.IOCloseUtils;
import com.zd.lib.utils.LogUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.http.GET;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PartMap;
import retrofit2.http.Path;
import retrofit2.http.Streaming;
import retrofit2.http.Url;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func0;
import rx.functions.Func1;

/**
 * @author zoudong
 *         上传图片，文件
 */
public class FileService {
    private static FileService ourInstance;

    public static FileService getInstance() {
        if (ourInstance == null)
            synchronized (FileService.class) {
                if (ourInstance == null)
                    ourInstance = new FileService();
            }
        return ourInstance;
    }

    public static final int TIMEOUT = 60;

    private FileService() {

    }

    private final String baseUrl = "https://upload.xin.com/upload.php/";

    public Observable<ImageUpEntity> ImageUpload(String filepath, final ProgressListener progressListener) {

        OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new ProgressRequestInterceptor(progressListener)).connectTimeout(TIMEOUT, TimeUnit.SECONDS).build();
        Retrofit retrofit = RetrofitUtils.getDefaultRetrofit(baseUrl, okHttpClient);
        Upload upload = retrofit.create(Upload.class);
        Map<String, RequestBody> param = new HashMap<>();
        File file = new File(filepath);
        param.put("app", MultipartBody.create(MultipartBody.FORM, "xin_app"));
        param.put("key", MultipartBody.create(MultipartBody.FORM, "RdUAb6GF6uWjqW"));
        RequestBody requestBody = MultipartBody.create(MultipartBody.FORM, file);
        param.put("pic\"; filename=\"*", requestBody);
        return upload.uploadImage(param)
                .compose(SchedulerCompat.<ImageUpEntity>applyIoSchedulers());  //在 doOnSubscribe()的后面跟一个 subscribeOn() ，就能指定准备工作的线程了。

    }

    public Observable<ResponseBody> downloadFile(String filename, final ProgressListener progressListener) {

        OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new ProgressResponseInterceptor(progressListener)).connectTimeout(TIMEOUT, TimeUnit.SECONDS).build();
        Retrofit retrofit = RetrofitUtils.getDefaultRetrofit(baseUrl, okHttpClient);
        Upload upload = retrofit.create(Upload.class);
        return upload.loadFile(filename)
                .compose(SchedulerCompat.<ResponseBody>applyNewSchedulers());   //在 doOnSubscribe()的后面跟一个 subscribeOn() ，就能指定准备工作的线程了。
    }

    public Observable<String> downloadFile(final String url, final String filename) {
        return downloadFile(url, filename, null);
    }


    public Observable<String> downloadFile(final String url, final String filename, final DownloadListener downloadListener) {
        LogUtils.d("zoudong", "downloadFile: " + url);
        final Observable<String> observable;
        File file = new File(filename);
        if (file.exists()) { // 异常处理 文件下载成功
            if (downloadListener != null) { //缓存回调
                downloadListener.onCacheFind(url, filename);
            }
            LogUtils.d("zoudong", "downloadFile: 读取视频缓存" + filename);
            observable = getCacheObservable(filename);
        } else {
            LogUtils.d("zoudong", "downloadFile: 下载视频" + filename);
            if (downloadListener != null) { //开始下载视频
                downloadListener.onStartDown(url, filename);
            }
            observable = getNetWorkObservable(url, filename, downloadListener);
        }
        return observable;
    }

    public Observable<String> getNetWorkObservable(final String url, final String filename, final DownloadListener downloadListener) {
        return  Observable.defer(new Func0<Observable<String>>() {
            @Override
            public Observable<String> call() {
                return getUpload(downloadListener).downloadFile(url)
                        .retryWhen(new RetryWithDelay(3, 1000))
                        .flatMap(new Func1<ResponseBody, Observable<String>>() {
                            @Override
                            public Observable<String> call(ResponseBody responseBody) {

                                try {
//                                    writeCache(responseBody, filename);
                                    writeResponseBodyToDisk(responseBody, filename);
                                } catch (IOException e) {
                                    e.printStackTrace();

                                    return Observable.error(e);
                                }
                                return Observable.just(filename);
                            }
                        });
            }
        });
    }

    @NonNull
    public Observable<String> getCacheObservable(final String filename) {
        return  Observable.defer(new Func0<Observable<String>>() {
            @Override
            public Observable<String> call() {
                return Observable.create(new Observable.OnSubscribe<String>() {
                    @Override
                    public void call(Subscriber<? super String> subscriber) {
                        if (!subscriber.isUnsubscribed()) {
                            subscriber.onNext(filename);
                            subscriber.onCompleted();
                        }

                    }
                });
            }
        });
    }
    public Upload getUpload(DownloadListener downloadListener) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(TIMEOUT, TimeUnit.SECONDS).build();
        builder.readTimeout(TIMEOUT, TimeUnit.SECONDS).build();
        builder.writeTimeout(TIMEOUT, TimeUnit.SECONDS).build();
        if (downloadListener != null) {
            builder.addInterceptor(new ProgressResponseInterceptor(downloadListener));
        }
        Retrofit retrofit = RetrofitUtils.getDefaultRetrofit(baseUrl, builder.build());
        return retrofit.create(Upload.class);
    }

    public interface Upload {
        @Multipart
        @POST("https://upload.xin.com/upload.php")
        Observable<ImageUpEntity> uploadImage(@PartMap Map<String, RequestBody> param);

        @Multipart
        @POST("http://upload.xin.com/upload_file.php")
        Observable<ResponseBody> uploadFile(@PartMap Map<String, RequestBody> param);

        @GET("http://app.youxinpai.com/xin/android-seller/{filename}")
        Observable<ResponseBody> loadFile(@Path("filename") String filename);

        @Streaming
        @GET
        Observable<ResponseBody> downloadFile(@Url String url);
    }

    public void  writeCache(ResponseBody responseBody, String fileName) throws IOException {
        String downingFilename = fileName + ".download";
        File downingFile = new File(downingFilename);
        if (!downingFile.getParentFile().exists()) { //创建文件件加
            downingFile.getParentFile().mkdirs();
        }

        long allLength;
        allLength = responseBody.contentLength();
        RandomAccessFile randomAccessFile = new RandomAccessFile(downingFilename, "rwd");
        FileChannel   channelOut = randomAccessFile.getChannel();
        MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE,
                0, allLength);
        byte[] buffer = new byte[1024 * 8];
        int len;
        int record = 0;
        while ((len = responseBody.byteStream().read(buffer)) != -1) {
            mappedBuffer.put(buffer, 0, len);
            record += len;
            LogUtils.d("zoudong", "file download: " + allLength + " of " + record + "---downingFilename=" + downingFilename);

        }
        File downSuccess = new File(fileName);
        downingFile.renameTo(downSuccess);
        LogUtils.e("zoudong", "writeCache: 成功");
        IOCloseUtils.close(channelOut);
        IOCloseUtils.close(randomAccessFile);
        IOCloseUtils.close(responseBody.byteStream());

    }

    private boolean writeResponseBodyToDisk(ResponseBody body,String fileName) throws IOException {
        String downingFilename = fileName + ".download";
        File tempfile = new File(downingFilename);
        if (!tempfile.getParentFile().exists()) {
            if (tempfile.mkdirs()) {
                LogUtils.e("zoudong", "writeResponseBodyToDisk: video 创建" + fileName);
            }
        }
        OutputStream fos = null;
        InputStream ios = null;
        try {
              fos = new FileOutputStream(tempfile);
              ios = body.byteStream();
            byte[] buffer = new byte[1024 * 5];
            int len;
            while ((len = ios.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            fos.flush();
            File downSuccess = new File(fileName);
            LogUtils.e("zoudong", "writeResponseBodyToDisk: 成功");
            return tempfile.renameTo(downSuccess);
        }finally {
            IOCloseUtils.close(fos);
            IOCloseUtils.close(ios);
        }

    }

    public class RetryWithDelay implements Func1<Observable<? extends Throwable>, Observable<?>> {

        private final int maxRetries;
        private final int retryDelayMillis;
        private int retryCount;

        public RetryWithDelay(int maxRetries, int retryDelayMillis) {
            this.maxRetries = maxRetries;
            this.retryDelayMillis = retryDelayMillis;
        }

        @Override
        public Observable<?> call(Observable<? extends Throwable> attempts) {
            return attempts
                    .flatMap(new Func1<Throwable, Observable<?>>() {
                        @Override
                        public Observable<?> call(Throwable throwable) {
                            if (++retryCount <= maxRetries && throwable instanceof IOException) {
                                LogUtils.e("zoudong", "get error, it will try after " + retryDelayMillis
                                        + " millisecond, retry count " + retryCount);
                                return Observable.timer(retryDelayMillis,
                                        TimeUnit.MILLISECONDS);
                            }
                            // Max retries hit. Just pass the error along.
                            return Observable.error(throwable);
                        }
                    });
        }
    }

    public static abstract class DownloadListener implements ProgressListener {
        public void onStartDown(String url, String filename) {
        }

        public void onCacheFind(String url, String filename) {
        }
    }
}
